﻿using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media.Animation;

namespace Gmantis.Controls
{
    [TemplatePart(Name = "ScrollViewer", Type = typeof(System.Windows.Controls.ScrollViewer))]
    public class ScrollViewer : ContentControl
    {
        // Fields
        internal System.Windows.Controls.ScrollViewer _elementScrollViewer;
        public static readonly DependencyProperty CanScrollDownProperty = DependencyProperty.Register("CanScrollDown", typeof(bool), typeof(ScrollViewer), null);
        public static readonly DependencyProperty CanScrollLeftProperty = DependencyProperty.Register("CanScrollLeft", typeof(bool), typeof(ScrollViewer), null);
        public static readonly DependencyProperty CanScrollRightProperty = DependencyProperty.Register("CanScrollRight", typeof(bool), typeof(ScrollViewer), null);
        public static readonly DependencyProperty CanScrollUpProperty = DependencyProperty.Register("CanScrollUp", typeof(bool), typeof(ScrollViewer), null);
        public static readonly DependencyProperty ContentTemplateSelectorProperty = DependencyProperty.Register("ContentTemplateSelector", typeof(DataTemplateSelector), typeof(ScrollViewer), new PropertyMetadata(null));
        public static readonly DependencyProperty ExtentHeightProperty = DependencyProperty.Register("ExtentHeight", typeof(double), typeof(ScrollViewer), null);
        public static readonly DependencyProperty ExtentWidthProperty = DependencyProperty.Register("ExtentWidth", typeof(double), typeof(ScrollViewer), null);
        public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register("HorizontalOffset", typeof(double), typeof(ScrollViewer), null);
        public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = DependencyProperty.Register("HorizontalScrollBarVisibility", typeof(Visibility), typeof(ScrollViewer), null);
        public static readonly DependencyProperty HorizontalSmallChangeProperty = DependencyProperty.Register("HorizontalSmallChange", typeof(double), typeof(ScrollViewer), new PropertyMetadata(20.0));
        public static readonly Command MoveBottomCommand = new Command("MoveBottom", typeof(ScrollViewer));
        public static readonly Command MoveDownCommand = new Command("MoveDown", typeof(ScrollViewer));
        public static readonly Command MoveLeftCommand = new Command("MoveLeft", typeof(ScrollViewer));
        public static readonly Command MoveLeftEndCommand = new Command("MoveLeftEnd", typeof(ScrollViewer));
        public static readonly Command MoveRightCommand = new Command("MoveRight", typeof(ScrollViewer));
        public static readonly Command MoveRightEndCommand = new Command("MoveRightEnd", typeof(ScrollViewer));
        public static readonly Command MoveTopCommand = new Command("MoveTop", typeof(ScrollViewer));
        public static readonly Command MoveUpCommand = new Command("MoveUp", typeof(ScrollViewer));
        public static readonly DependencyProperty ScrollableHeightProperty = DependencyProperty.Register("ScrollableHeight", typeof(double), typeof(ScrollViewer), null);
        public static readonly DependencyProperty ScrollableWidthProperty = DependencyProperty.Register("ScrollableWidth", typeof(double), typeof(ScrollViewer), null);
        public static readonly DependencyProperty ScrollModeProperty = DependencyProperty.Register("ScrollMode", typeof(ClickMode), typeof(ScrollViewer), null);
        internal const string ScrollViewerElementName = "ScrollViewer";
        public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register("VerticalOffset", typeof(double), typeof(ScrollViewer), null);
        public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = DependencyProperty.Register("VerticalScrollBarVisibility", typeof(Visibility), typeof(ScrollViewer), null);
        public static readonly DependencyProperty VerticalSmallChangeProperty = DependencyProperty.Register("VerticalSmallChange", typeof(double), typeof(ScrollViewer), new PropertyMetadata(20.0));
        public static readonly DependencyProperty ViewportHeightProperty = DependencyProperty.Register("ViewportHeight", typeof(double), typeof(ScrollViewer), null);
        public static readonly DependencyProperty ViewportWidthProperty = DependencyProperty.Register("ViewportWidth", typeof(double), typeof(ScrollViewer), null);

        // Methods
        static ScrollViewer()
        {
            CommandManager.RegisterClassCommandBinding(typeof(ScrollViewer), new CommandBinding(MoveUpCommand, new ExecutedRoutedEventHandler(ScrollViewer.OnCommand)));
            CommandManager.RegisterClassCommandBinding(typeof(ScrollViewer), new CommandBinding(MoveDownCommand, new ExecutedRoutedEventHandler(ScrollViewer.OnCommand)));
            CommandManager.RegisterClassCommandBinding(typeof(ScrollViewer), new CommandBinding(MoveRightCommand, new ExecutedRoutedEventHandler(ScrollViewer.OnCommand)));
            CommandManager.RegisterClassCommandBinding(typeof(ScrollViewer), new CommandBinding(MoveLeftCommand, new ExecutedRoutedEventHandler(ScrollViewer.OnCommand)));
            CommandManager.RegisterClassCommandBinding(typeof(ScrollViewer), new CommandBinding(MoveTopCommand, new ExecutedRoutedEventHandler(ScrollViewer.OnCommand)));
            CommandManager.RegisterClassCommandBinding(typeof(ScrollViewer), new CommandBinding(MoveBottomCommand, new ExecutedRoutedEventHandler(ScrollViewer.OnCommand)));
            CommandManager.RegisterClassCommandBinding(typeof(ScrollViewer), new CommandBinding(MoveRightEndCommand, new ExecutedRoutedEventHandler(ScrollViewer.OnCommand)));
            CommandManager.RegisterClassCommandBinding(typeof(ScrollViewer), new CommandBinding(MoveLeftEndCommand, new ExecutedRoutedEventHandler(ScrollViewer.OnCommand)));
        }

        public ScrollViewer()
        {
            base.DefaultStyleKey = typeof(ScrollViewer);
        }

        private void _elementScrollViewer_LayoutUpdated(object sender, EventArgs e)
        {
            bool flag = this.VerticalScrollBarVisibility.ToBool() && this._elementScrollViewer.ComputedVerticalScrollBarVisibility.ToBool();
            this.CanScrollUp = (flag && (this.VerticalOffset > 0.0)) && (this.ScrollableHeight > 0.0);
            this.CanScrollDown = (flag && (this.VerticalOffset < (this.ExtentHeight - this.ViewportHeight))) && (this.ScrollableHeight > 0.0);
            bool flag2 = this.HorizontalScrollBarVisibility.ToBool() && this._elementScrollViewer.ComputedHorizontalScrollBarVisibility.ToBool();
            this.CanScrollLeft = (flag2 && (this.HorizontalOffset > 0.0)) && (this.ScrollableWidth > 0.0);
            this.CanScrollRight = (flag2 && (this.HorizontalOffset < (this.ExtentWidth - this.ViewportWidth))) && (this.ScrollableWidth > 0.0);
        }

        private void EnsureIsLoaded()
        {
            if (this._elementScrollViewer == null)
            {
                throw new Exception("Template not applied");
            }
        }

        private T GetTemplateChild<T>(string childName, bool required, ref string errors) where T : class
        {
            DependencyObject templateChild = base.GetTemplateChild(childName);
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "template part", childName, templateChild, required, ref errors);
            return (templateChild as T);
        }

        private static Storyboard GetTemplateChildResource(FrameworkElement root, string resourceName, bool required, ref string errors)
        {
            return GetTemplateChildResource<Storyboard>(root, resourceName, required, ref errors);
        }

        private static T GetTemplateChildResource<T>(FrameworkElement root, string resourceName, bool required, ref string errors) where T : class
        {
            object child = root.Resources[resourceName];
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "resource", resourceName, child, required, ref errors);
            return (child as T);
        }

        private void InitializeScrollViewerPart()
        {
            base.SetBinding(VerticalOffsetProperty, new Binding().From<System.Windows.Controls.ScrollViewer>(this._elementScrollViewer, x => x.VerticalOffset));
            base.SetBinding(HorizontalOffsetProperty, new Binding().From<System.Windows.Controls.ScrollViewer>(this._elementScrollViewer, x => x.HorizontalOffset));
            base.SetBinding(ExtentHeightProperty, new Binding().From<System.Windows.Controls.ScrollViewer>(this._elementScrollViewer, x => x.ExtentHeight));
            base.SetBinding(ExtentWidthProperty, new Binding().From<System.Windows.Controls.ScrollViewer>(this._elementScrollViewer, x => x.ExtentWidth));
            base.SetBinding(ScrollableHeightProperty, new Binding().From<System.Windows.Controls.ScrollViewer>(this._elementScrollViewer, x => x.ScrollableHeight));
            base.SetBinding(ScrollableWidthProperty, new Binding().From<System.Windows.Controls.ScrollViewer>(this._elementScrollViewer, x => x.ScrollableWidth));
            base.SetBinding(ViewportHeightProperty, new Binding().From<System.Windows.Controls.ScrollViewer>(this._elementScrollViewer, x => x.ViewportHeight));
            base.SetBinding(ViewportWidthProperty, new Binding().From<System.Windows.Controls.ScrollViewer>(this._elementScrollViewer, x => x.ViewportWidth));
            this._elementScrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
            this._elementScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Visible;
            this._elementScrollViewer.LayoutUpdated += new EventHandler(this._elementScrollViewer_LayoutUpdated);
        }

        public override void OnApplyTemplate()
        {
            this.OnBeforeApplyTemplate();
            string errors = string.Empty;
            base.OnApplyTemplate();
            this._elementScrollViewer = this.GetTemplateChild<System.Windows.Controls.ScrollViewer>("ScrollViewer", true, ref errors);
            if (this._elementScrollViewer != null)
            {
                this.InitializeScrollViewerPart();
            }
            if ((!string.IsNullOrEmpty(errors) && (base.Template != null)) && !PlatformIndependent.IsInDesignMode(this))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Template cannot be applied to ScrollViewer.\nDetails: {0}", new object[] { errors }));
            }
        }

        private void OnBeforeApplyTemplate()
        {
            DataTemplate contentTemplate = null;
            if (this.ContentTemplateSelector != null)
            {
                contentTemplate = this.ContentTemplateSelector.SelectTemplate(base.Content, this);
            }
            if (base.ContentTemplate != null)
            {
                contentTemplate = base.ContentTemplate;
            }
            base.ContentTemplate = contentTemplate;
            if (this._elementScrollViewer != null)
            {
                this._elementScrollViewer.LayoutUpdated -= new EventHandler(this._elementScrollViewer_LayoutUpdated);
            }
        }

        private static void OnCommand(object target, ExecutedRoutedEventArgs args)
        {
            ScrollViewer viewer = (ScrollViewer)target;
            if (args.Command == MoveUpCommand)
            {
                viewer.ScrollUp();
            }
            else if (args.Command == MoveDownCommand)
            {
                viewer.ScrollDown();
            }
            else if (args.Command == MoveRightCommand)
            {
                viewer.ScrollRight();
            }
            else if (args.Command == MoveLeftCommand)
            {
                viewer.ScrollLeft();
            }
            else if (args.Command == MoveTopCommand)
            {
                viewer.ScrollToTop();
            }
            else if (args.Command == MoveBottomCommand)
            {
                viewer.ScrollToBottom();
            }
            else if (args.Command == MoveRightEndCommand)
            {
                viewer.ScrollToRightEnd();
            }
            else if (args.Command == MoveLeftEndCommand)
            {
                viewer.ScrollToLeftEnd();
            }
        }

        public void ScrollDown()
        {
            this.ScrollToVerticalOffset(this.VerticalOffset + this.VerticalSmallChange);
        }

        public void ScrollLeft()
        {
            this.ScrollToHorizontalOffset(this.HorizontalOffset - this.HorizontalSmallChange);
        }

        public void ScrollRight()
        {
            this.ScrollToHorizontalOffset(this.HorizontalOffset + this.HorizontalSmallChange);
        }

        public void ScrollToBottom()
        {
            this.ScrollToVerticalOffset(this.ExtentHeight - this.ViewportHeight);
        }

        public void ScrollToEnd()
        {
            this.ScrollToBottom();
            this.ScrollToRightEnd();
        }

        public void ScrollToHome()
        {
            this.ScrollToTop();
            this.ScrollToLeftEnd();
        }

        public void ScrollToHorizontalOffset(double offset)
        {
            this.EnsureIsLoaded();
            this._elementScrollViewer.ScrollToHorizontalOffset(offset);
        }

        public void ScrollToLeftEnd()
        {
            this.ScrollToVerticalOffset(0.0);
        }

        public void ScrollToRightEnd()
        {
            this.ScrollToHorizontalOffset(this.ExtentWidth - this.ViewportWidth);
        }

        public void ScrollToTop()
        {
            this.ScrollToVerticalOffset(0.0);
        }

        public void ScrollToVerticalOffset(double offset)
        {
            this.EnsureIsLoaded();
            this._elementScrollViewer.ScrollToVerticalOffset(offset);
        }

        public void ScrollUp()
        {
            this.ScrollToVerticalOffset(this.VerticalOffset - this.VerticalSmallChange);
        }

        // Properties
        public bool CanScrollDown
        {
            get { return (bool)GetValue(CanScrollDownProperty); }
            set { SetValue(CanScrollDownProperty, value); }
        }

        public bool CanScrollLeft
        {
            get { return (bool)GetValue(CanScrollLeftProperty); }
            set { SetValue(CanScrollLeftProperty, value); }
        }

        public bool CanScrollRight
        {
            get { return (bool)GetValue(CanScrollRightProperty); }
            set { SetValue(CanScrollRightProperty, value); }
        }

        public bool CanScrollUp
        {
            get { return (bool)GetValue(CanScrollUpProperty); }
            set { SetValue(CanScrollUpProperty, value); }
        }

        public DataTemplateSelector ContentTemplateSelector
        {
            get { return (DataTemplateSelector)GetValue(ContentTemplateSelectorProperty); }
            set { SetValue(ContentTemplateSelectorProperty, value); }
        }

        public double ExtentHeight
        {
            get { return (double)GetValue(ExtentHeightProperty); }
        }

        public double ExtentWidth
        {
            get { return (double)GetValue(ExtentWidthProperty); }
        }

        public double HorizontalOffset
        {
            get { return (double)GetValue(HorizontalOffsetProperty); }
        }

        public Visibility HorizontalScrollBarVisibility
        {
            get { return (Visibility)GetValue(HorizontalScrollBarVisibilityProperty); }
            set { SetValue(HorizontalScrollBarVisibilityProperty, value); }
        }

        public double HorizontalSmallChange
        {
            get { return (double)GetValue(HorizontalSmallChangeProperty); }
            set { SetValue(HorizontalSmallChangeProperty, value); }
        }

        public double ScrollableHeight
        {
            get { return (double)GetValue(ScrollableHeightProperty); }
            set { SetValue(ScrollableHeightProperty, value); }
        }

        public double ScrollableWidth
        {
            get { return (double)GetValue(ScrollableWidthProperty); }
            set { SetValue(ScrollableWidthProperty, value); }
        }

        public ClickMode ScrollMode
        {
            get { return (ClickMode)GetValue(ScrollModeProperty); }
            set { SetValue(ScrollModeProperty, value); }
        }

        public double VerticalOffset
        {
            get { return (double)GetValue(VerticalOffsetProperty); }
        }

        public Visibility VerticalScrollBarVisibility
        {
            get { return (Visibility)GetValue(VerticalScrollBarVisibilityProperty); }
            set { SetValue(VerticalScrollBarVisibilityProperty, value); }
        }

        public double VerticalSmallChange
        {
            get { return (double)GetValue(VerticalSmallChangeProperty); }
            set { SetValue(VerticalSmallChangeProperty, value); }
        }

        public double ViewportHeight
        {
            get { return (double)GetValue(ViewportHeightProperty); }
        }

        public double ViewportWidth
        {
            get { return (double)GetValue(ViewportWidthProperty); }
        }
    }
}
